 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.core.internal.registry;

 import java.io.IOException ;
 import java.util.*;
 import javax.xml.parsers.ParserConfigurationException ;
 import javax.xml.parsers.SAXParserFactory ;
 import org.eclipse.core.runtime.*;
 import org.eclipse.osgi.util.NLS;
 import org.xml.sax.*;
 import org.xml.sax.helpers.DefaultHandler ;

 public class ExtensionsParser extends DefaultHandler {
     // Introduced for backward compatibility
 private final static String NO_EXTENSION_MUNGING = "eclipse.noExtensionMunging"; //$NON-NLS-1$ //System property
 private static final String VERSION_3_0 = "3.0"; //$NON-NLS-1$
 private static final String VERSION_3_2 = "3.2"; //$NON-NLS-1$
 private static Map extensionPointMap;

     static {
         initializeExtensionPointMap();
     }

     /**
      * Initialize the list of renamed extension point ids
      */
     private static void initializeExtensionPointMap() {
         Map map = new HashMap(13);
         map.put("org.eclipse.ui.markerImageProvider", "org.eclipse.ui.ide.markerImageProvider"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.markerHelp", "org.eclipse.ui.ide.markerHelp"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.markerImageProviders", "org.eclipse.ui.ide.markerImageProviders"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.markerResolution", "org.eclipse.ui.ide.markerResolution"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.projectNatureImages", "org.eclipse.ui.ide.projectNatureImages"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.resourceFilters", "org.eclipse.ui.ide.resourceFilters"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.markerUpdaters", "org.eclipse.ui.editors.markerUpdaters"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.documentProviders", "org.eclipse.ui.editors.documentProviders"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.ui.workbench.texteditor.markerAnnotationSpecification", "org.eclipse.ui.editors.markerAnnotationSpecification"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.help.browser", "org.eclipse.help.base.browser"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.help.luceneAnalyzer", "org.eclipse.help.base.luceneAnalyzer"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.help.webapp", "org.eclipse.help.base.webapp"); //$NON-NLS-1$ //$NON-NLS-2$
 map.put("org.eclipse.help.support", "org.eclipse.ui.helpSupport"); //$NON-NLS-1$ //$NON-NLS-2$
 extensionPointMap = map;
     }

     private static long cumulativeTime = 0;

     // is in compatibility mode
 private boolean compatibilityMode;

     // File name for this extension manifest
 // This to help with error reporting
 private String locationName = null;

     // Current State Information
 private Stack stateStack = new Stack();

     // Current object stack (used to hold the current object we are
 // populating in this plugin descriptor
 private Stack objectStack = new Stack();

     private String schemaVersion = null;

     // A status for holding results.
 private MultiStatus status;

     // Owning extension registry
 private ExtensionRegistry registry;

     // Resource bundle used to translate the content of the plugin.xml
 protected ResourceBundle resources;

     // Keep track of the object encountered.
 private RegistryObjectManager objectManager;

     private Contribution contribution;

     //This keeps tracks of the value of the configuration element in case the value comes in several pieces (see characters()). See as well bug 75592.
 private String configurationElementValue;

     /**
      * Status code constant (value 1) indicating a problem in a bundle extensions
      * manifest (<code>extensions.xml</code>) file.
      */
     public static final int PARSE_PROBLEM = 1;

     public static final String PLUGIN = "plugin"; //$NON-NLS-1$
 public static final String PLUGIN_ID = "id"; //$NON-NLS-1$
 public static final String PLUGIN_NAME = "name"; //$NON-NLS-1$
 public static final String FRAGMENT = "fragment"; //$NON-NLS-1$
 public static final String BUNDLE_UID = "id"; //$NON-NLS-1$

     public static final String EXTENSION_POINT = "extension-point"; //$NON-NLS-1$
 public static final String EXTENSION_POINT_NAME = "name"; //$NON-NLS-1$
 public static final String EXTENSION_POINT_ID = "id"; //$NON-NLS-1$
 public static final String EXTENSION_POINT_SCHEMA = "schema"; //$NON-NLS-1$

     public static final String EXTENSION = "extension"; //$NON-NLS-1$
 public static final String EXTENSION_NAME = "name"; //$NON-NLS-1$
 public static final String EXTENSION_ID = "id"; //$NON-NLS-1$
 public static final String EXTENSION_TARGET = "point"; //$NON-NLS-1$

     public static final String ELEMENT = "element"; //$NON-NLS-1$
 public static final String ELEMENT_NAME = "name"; //$NON-NLS-1$
 public static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$

     public static final String PROPERTY = "property"; //$NON-NLS-1$
 public static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
 public static final String PROPERTY_VALUE = "value"; //$NON-NLS-1$

     // Valid States
 private static final int IGNORED_ELEMENT_STATE = 0;
     private static final int INITIAL_STATE = 1;
     private static final int BUNDLE_STATE = 2;
     private static final int BUNDLE_EXTENSION_POINT_STATE = 5;
     private static final int BUNDLE_EXTENSION_STATE = 6;
     private static final int CONFIGURATION_ELEMENT_STATE = 10;

     // Keep a group of vectors as a temporary scratch space. These
 // vectors will be used to populate arrays in the bundle model
 // once processing of the XML file is complete.
 private static final int EXTENSION_POINT_INDEX = 0;
     private static final int EXTENSION_INDEX = 1;
     private static final int LAST_INDEX = 1;

     private ArrayList scratchVectors[] = new ArrayList[LAST_INDEX + 1];

     private Locator locator = null;

     // Cache the behavior toggle (true: attempt to extract namespace from qualified IDs)
 private boolean extractNamespaces = false;

     private ArrayList processedExtensionIds = null;

     public ExtensionsParser(MultiStatus status, ExtensionRegistry registry) {
         super();
         this.status = status;
         this.registry = registry;
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
      */
     public void setDocumentLocator(Locator locator) {
         this.locator = locator;
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
      */
     public void characters(char[] ch, int start, int length) {
         int state = ((Integer ) stateStack.peek()).intValue();
         if (state != CONFIGURATION_ELEMENT_STATE)
             return;
         if (state == CONFIGURATION_ELEMENT_STATE) {
             // Accept character data within an element, is when it is
 // part of a configuration element (i.e. an element within an EXTENSION element
 ConfigurationElement currentConfigElement = (ConfigurationElement) objectStack.peek();
             String value = new String (ch, start, length);
             if (configurationElementValue == null) {
                 if (value.trim().length() != 0) {
                     configurationElementValue = value;
                 }
             } else {
                 configurationElementValue = configurationElementValue + value;
             }
             if (configurationElementValue != null)
                 currentConfigElement.setValue(translate(configurationElementValue));
         }
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#endDocument()
      */
     public void endDocument() {
         // do nothing
 }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
      */
     public void endElement(String uri, String elementName, String qName) {
         switch (((Integer ) stateStack.peek()).intValue()) {
             case IGNORED_ELEMENT_STATE :
                 stateStack.pop();
                 break;
             case INITIAL_STATE :
                 // shouldn't get here
 internalError(NLS.bind(RegistryMessages.parse_internalStack, elementName));
                 break;
             case BUNDLE_STATE :
                 stateStack.pop();

                 ArrayList extensionPoints = scratchVectors[EXTENSION_POINT_INDEX];
                 ArrayList extensions = scratchVectors[EXTENSION_INDEX];
                 int[] namespaceChildren = new int[2 + extensionPoints.size() + extensions.size()];
                 int position = 2;
                 // Put the extension points into this namespace
 if (extensionPoints.size() > 0) {
                     namespaceChildren[Contribution.EXTENSION_POINT] = extensionPoints.size();
                     for (Iterator iter = extensionPoints.iterator(); iter.hasNext();) {
                         namespaceChildren[position++] = ((RegistryObject) iter.next()).getObjectId();
                     }
                     extensionPoints.clear();
                 }

                 // Put the extensions into this namespace too
 if (extensions.size() > 0) {
                     Extension[] renamedExtensions = fixRenamedExtensionPoints((Extension[]) extensions.toArray(new Extension[extensions.size()]));
                     namespaceChildren[Contribution.EXTENSION] = renamedExtensions.length;
                     for (int i = 0; i < renamedExtensions.length; i++) {
                         namespaceChildren[position++] = renamedExtensions[i].getObjectId();
                     }
                     extensions.clear();
                 }
                 contribution.setRawChildren(namespaceChildren);
                 break;
             case BUNDLE_EXTENSION_POINT_STATE :
                 if (elementName.equals(EXTENSION_POINT)) {
                     stateStack.pop();
                 }
                 break;
             case BUNDLE_EXTENSION_STATE :
                 if (elementName.equals(EXTENSION)) {
                     stateStack.pop();
                     // Finish up extension object
 Extension currentExtension = (Extension) objectStack.pop();
                     if (currentExtension.getNamespaceIdentifier() == null)
                         currentExtension.setNamespaceIdentifier(contribution.getDefaultNamespace());
                     currentExtension.setContributorId(contribution.getContributorId());
                     scratchVectors[EXTENSION_INDEX].add(currentExtension);
                 }
                 break;
             case CONFIGURATION_ELEMENT_STATE :
                 // We don't care what the element name was
 stateStack.pop();
                 // Now finish up the configuration element object
 configurationElementValue = null;
                 ConfigurationElement currentConfigElement = (ConfigurationElement) objectStack.pop();

                 String value = currentConfigElement.getValueAsIs();
                 if (value != null) {
                     currentConfigElement.setValue(value.trim());
                 }

                 RegistryObject parent = (RegistryObject) objectStack.peek();
                 // Want to add this configuration element to the subelements of an extension
 int[] oldValues = parent.getRawChildren();
                 int size = oldValues.length;
                 int[] newValues = new int[size + 1];
                 for (int i = 0; i < size; i++) {
                     newValues[i] = oldValues[i];
                 }
                 newValues[size] = currentConfigElement.getObjectId();
                 parent.setRawChildren(newValues);
                 currentConfigElement.setParentId(parent.getObjectId());
                 currentConfigElement.setParentType(parent instanceof ConfigurationElement ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.EXTENSION);
                 break;
         }
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
      */
     public void error(SAXParseException ex) {
         logStatus(ex);
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
      */
     public void fatalError(SAXParseException ex) throws SAXException {
         logStatus(ex);
         throw ex;
     }

     private void handleExtensionPointState(String elementName) {
         // We ignore all elements under extension points (if there are any)
 stateStack.push(new Integer (IGNORED_ELEMENT_STATE));
         unknownElement(EXTENSION_POINT, elementName);
     }

     private void handleExtensionState(String elementName, Attributes attributes) {
         // You need to change the state here even though we will be executing the same
 // code for ExtensionState and ConfigurationElementState. We ignore the name
 // of the element for ConfigurationElements. When we are wrapping up, we will
 // want to add each configuration element object to the subElements vector of
 // its parent configuration element object. However, the first configuration
 // element object we created (the last one we pop off the stack) will need to
 // be added to a vector in the extension object called _configuration.
 stateStack.push(new Integer (CONFIGURATION_ELEMENT_STATE));

         configurationElementValue = null;

         // create a new Configuration Element and push it onto the object stack
 ConfigurationElement currentConfigurationElement = registry.getElementFactory().createConfigurationElement(contribution.shouldPersist());
         currentConfigurationElement.setContributorId(contribution.getContributorId());
         objectStack.push(currentConfigurationElement);
         currentConfigurationElement.setName(elementName);

         // Processing the attributes of a configuration element involves creating
 // a new configuration property for each attribute and populating the configuration
 // property with the name/value pair of the attribute. Note there will be one
 // configuration property for each attribute
 parseConfigurationElementAttributes(attributes);
         objectManager.add(currentConfigurationElement, true);
     }

     private void handleInitialState(String elementName, Attributes attributes) {
         // new manifests should have the plugin (or fragment) element empty
 // in compatibility mode, any extraneous elements will be silently ignored
 compatibilityMode = attributes.getLength() > 0;
         stateStack.push(new Integer (BUNDLE_STATE));
         objectStack.push(contribution);
     }

     private void handleBundleState(String elementName, Attributes attributes) {
         if (elementName.equals(EXTENSION_POINT)) {
             stateStack.push(new Integer (BUNDLE_EXTENSION_POINT_STATE));
             parseExtensionPointAttributes(attributes);
             return;
         }
         if (elementName.equals(EXTENSION)) {
             stateStack.push(new Integer (BUNDLE_EXTENSION_STATE));
             parseExtensionAttributes(attributes);
             return;
         }

         // If we get to this point, the element name is one we don't currently accept.
 // Set the state to indicate that this element will be ignored
 stateStack.push(new Integer (IGNORED_ELEMENT_STATE));
         if (!compatibilityMode)
             unknownElement(PLUGIN, elementName);
     }

     private void logStatus(SAXParseException ex) {
         String name = ex.getSystemId();
         if (name == null)
             name = locationName;
         if (name == null)
             name = ""; //$NON-NLS-1$
 else
             name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$

         String msg;
         if (name.equals("")) //$NON-NLS-1$
 msg = NLS.bind(RegistryMessages.parse_error, ex.getMessage());
         else
             msg = NLS.bind(RegistryMessages.parse_errorNameLineColumn, (new Object [] {name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()}));
         error(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, PARSE_PROBLEM, msg, ex));
     }

     public Contribution parseManifest(SAXParserFactory factory, InputSource in, String manifestName, RegistryObjectManager registryObjects, Contribution currentNamespace, ResourceBundle bundle) throws ParserConfigurationException , SAXException, IOException {
         long start = 0;
         this.resources = bundle;
         this.objectManager = registryObjects;
         //initialize the parser with this object
 this.contribution = currentNamespace;
         if (registry.debug())
             start = System.currentTimeMillis();

         if (factory == null)
             throw new SAXException(RegistryMessages.parse_xmlParserNotAvailable);

         try {
             locationName = in.getSystemId();
             if (locationName == null)
                 locationName = manifestName;
             factory.setNamespaceAware(true);
             try {
                 factory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$
 } catch (SAXException se) {
                 // ignore; we can still operate without string-interning
 }
             factory.setValidating(false);
             factory.newSAXParser().parse(in, this);
             return (Contribution) objectStack.pop();
         } finally {
             if (registry.debug()) {
                 cumulativeTime = cumulativeTime + (System.currentTimeMillis() - start);
                 System.out.println("Cumulative parse time so far : " + cumulativeTime); //$NON-NLS-1$
 }
         }
     }

     private void parseConfigurationElementAttributes(Attributes attributes) {
         ConfigurationElement parentConfigurationElement = (ConfigurationElement) objectStack.peek();

         // process attributes
 int len = (attributes != null) ? attributes.getLength() : 0;
         if (len == 0) {
             parentConfigurationElement.setProperties(RegistryObjectManager.EMPTY_STRING_ARRAY);
             return;
         }
         String [] properties = new String [len * 2];
         for (int i = 0; i < len; i++) {
             properties[i * 2] = attributes.getLocalName(i);
             properties[i * 2 + 1] = translate(attributes.getValue(i));
         }
         parentConfigurationElement.setProperties(properties);
         properties = null;
     }

     private void parseExtensionAttributes(Attributes attributes) {
         Extension currentExtension = registry.getElementFactory().createExtension(contribution.shouldPersist());
         objectStack.push(currentExtension);

         String simpleId = null;
         String namespaceName = null;
         // Process Attributes
 int len = (attributes != null) ? attributes.getLength() : 0;
         for (int i = 0; i < len; i++) {
             String attrName = attributes.getLocalName(i);
             String attrValue = attributes.getValue(i).trim();

             if (attrName.equals(EXTENSION_NAME))
                 currentExtension.setLabel(translate(attrValue));
             else if (attrName.equals(EXTENSION_ID)) {
                 int simpleIdStart = attrValue.lastIndexOf('.');
                 if ((simpleIdStart != -1) && extractNamespaces) {
                     simpleId = attrValue.substring(simpleIdStart + 1);
                     namespaceName = attrValue.substring(0, simpleIdStart);
                 } else {
                     simpleId = attrValue;
                     namespaceName = contribution.getDefaultNamespace();
                 }
                 currentExtension.setSimpleIdentifier(simpleId);
                 currentExtension.setNamespaceIdentifier(namespaceName);
             } else if (attrName.equals(EXTENSION_TARGET)) {
                 // check if point is specified as a simple or qualified name
 String targetName;
                 if (attrValue.lastIndexOf('.') == -1) {
                     String baseId = contribution.getDefaultNamespace();
                     targetName = baseId + '.' + attrValue;
                 } else
                     targetName = attrValue;
                 currentExtension.setExtensionPointIdentifier(targetName);
             } else
                 unknownAttribute(EXTENSION, attrName);
         }
         if (currentExtension.getExtensionPointIdentifier() == null) {
             missingAttribute(EXTENSION_TARGET, EXTENSION);
             stateStack.pop();
             stateStack.push(new Integer (IGNORED_ELEMENT_STATE));
             objectStack.pop();
             return;
         }
         // if we have an Id specified, check for duplicates. Only issue warning (not error) if duplicate found
 // as it might still work fine - depending on the access pattern.
 if (simpleId != null && registry.debug()) {
             String uniqueId = namespaceName + '.' + simpleId;
             IExtension existingExtension = registry.getExtension(uniqueId);
             if (existingExtension != null) {
                 String currentSupplier = contribution.getDefaultNamespace();
                 String existingSupplier = existingExtension.getContributor().getName();
                 String msg = NLS.bind(RegistryMessages.parse_duplicateExtension, new String [] {currentSupplier, existingSupplier, uniqueId});
                 registry.log(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, 0, msg, null));
             } else if (processedExtensionIds != null) { // check elements in this contribution
 for (Iterator i = processedExtensionIds.iterator(); i.hasNext();) {
                     if (uniqueId.equals(i.next())) {
                         String currentSupplier = contribution.getDefaultNamespace();
                         String existingSupplier = currentSupplier;
                         String msg = NLS.bind(RegistryMessages.parse_duplicateExtension, new String [] {currentSupplier, existingSupplier, uniqueId});
                         registry.log(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, 0, msg, null));
                         break;
                     }
                 }
             }
             if (processedExtensionIds == null)
                 processedExtensionIds = new ArrayList(10);
             processedExtensionIds.add(uniqueId);
         }

         objectManager.add(currentExtension, true);
     }

     //todo: Are all three methods needed??
 private void missingAttribute(String attribute, String element) {
         if (locator == null)
             internalError(NLS.bind(RegistryMessages.parse_missingAttribute, attribute, element));
         else
             internalError(NLS.bind(RegistryMessages.parse_missingAttributeLine, (new String [] {attribute, element, Integer.toString(locator.getLineNumber())})));
     }

     private void unknownAttribute(String attribute, String element) {
         if (locator == null)
             internalError(NLS.bind(RegistryMessages.parse_unknownAttribute, attribute, element));
         else
             internalError(NLS.bind(RegistryMessages.parse_unknownAttributeLine, (new String [] {attribute, element, Integer.toString(locator.getLineNumber())})));
     }

     private void unknownElement(String parent, String element) {
         if (locator == null)
             internalError(NLS.bind(RegistryMessages.parse_unknownElement, element, parent));
         else
             internalError(NLS.bind(RegistryMessages.parse_unknownElementLine, (new String [] {element, parent, Integer.toString(locator.getLineNumber())})));
     }

     private void parseExtensionPointAttributes(Attributes attributes) {
         ExtensionPoint currentExtPoint = registry.getElementFactory().createExtensionPoint(contribution.shouldPersist());

         // Process Attributes
 int len = (attributes != null) ? attributes.getLength() : 0;
         for (int i = 0; i < len; i++) {
             String attrName = attributes.getLocalName(i);
             String attrValue = attributes.getValue(i).trim();

             if (attrName.equals(EXTENSION_POINT_NAME))
                 currentExtPoint.setLabel(translate(attrValue));
             else if (attrName.equals(EXTENSION_POINT_ID)) {
                 String uniqueId;
                 String namespaceName;
                 int simpleIdStart = attrValue.lastIndexOf('.');
                 if (simpleIdStart != -1 && extractNamespaces) {
                     namespaceName = attrValue.substring(0, simpleIdStart);
                     uniqueId = attrValue;
                 } else {
                     namespaceName = contribution.getDefaultNamespace();
                     uniqueId = namespaceName + '.' + attrValue;
                 }
                 currentExtPoint.setUniqueIdentifier(uniqueId);
                 currentExtPoint.setNamespace(namespaceName);

             } else if (attrName.equals(EXTENSION_POINT_SCHEMA))
                 currentExtPoint.setSchema(attrValue);
             else
                 unknownAttribute(EXTENSION_POINT, attrName);
         }
         if (currentExtPoint.getSimpleIdentifier() == null || currentExtPoint.getLabel() == null) {
             String attribute = currentExtPoint.getSimpleIdentifier() == null ? EXTENSION_POINT_ID : EXTENSION_POINT_NAME;
             missingAttribute(attribute, EXTENSION_POINT);
             stateStack.pop();
             stateStack.push(new Integer (IGNORED_ELEMENT_STATE));
             return;
         }
         if (!objectManager.addExtensionPoint(currentExtPoint, true)) {
             // avoid adding extension point second time as it might cause
 // extensions associated with the existing extension point to
 // become inaccessible.
 if (registry.debug()) {
                 String msg = NLS.bind(RegistryMessages.parse_duplicateExtensionPoint, currentExtPoint.getUniqueIdentifier(), contribution.getDefaultNamespace());
                 registry.log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, msg, null));
             }
             stateStack.pop();
             stateStack.push(new Integer (IGNORED_ELEMENT_STATE));
             return;
         }
         if (currentExtPoint.getNamespace() == null)
             currentExtPoint.setNamespace(contribution.getDefaultNamespace());
         currentExtPoint.setContributorId(contribution.getContributorId());

         // Now populate the the vector just below us on the objectStack with this extension point
 scratchVectors[EXTENSION_POINT_INDEX].add(currentExtPoint);
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#startDocument()
      */
     public void startDocument() {
         stateStack.push(new Integer (INITIAL_STATE));
         for (int i = 0; i <= LAST_INDEX; i++) {
             scratchVectors[i] = new ArrayList();
         }
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
      */
     public void startElement(String uri, String elementName, String qName, Attributes attributes) {
         switch (((Integer ) stateStack.peek()).intValue()) {
             case INITIAL_STATE :
                 handleInitialState(elementName, attributes);
                 break;
             case BUNDLE_STATE :
                 handleBundleState(elementName, attributes);
                 break;
             case BUNDLE_EXTENSION_POINT_STATE :
                 handleExtensionPointState(elementName);
                 break;
             case BUNDLE_EXTENSION_STATE :
             case CONFIGURATION_ELEMENT_STATE :
                 handleExtensionState(elementName, attributes);
                 break;
             default :
                 stateStack.push(new Integer (IGNORED_ELEMENT_STATE));
                 if (!compatibilityMode)
                     internalError(NLS.bind(RegistryMessages.parse_unknownTopElement, elementName));
         }
     }

     /* (non-Javadoc)
      * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
      */
     public void warning(SAXParseException ex) {
         logStatus(ex);
     }

     private void internalError(String message) {
         error(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, PARSE_PROBLEM, message, null));
     }

     /* (non-Javadoc)
      * @see org.xml.sax.ContentHandler#processingInstruction
      * @since 3.0
      */
     public void processingInstruction(String target, String data) throws SAXException {
         // Since 3.0, a processing instruction of the form <?eclipse version="3.0"?> at
 // the start of the manifest file is used to indicate the plug-in manifest
 // schema version in effect. Pre-3.0 (i.e., 2.1) plug-in manifest files do not
 // have one of these, and this is how we can distinguish the manifest of a
 // pre-3.0 plug-in from a post-3.0 one (for compatibility transformations).
 if (target.equalsIgnoreCase("eclipse")) { //$NON-NLS-1$
 // just the presence of this processing instruction indicates that this
 // plug-in is at least 3.0
 schemaVersion = VERSION_3_0;
             StringTokenizer tokenizer = new StringTokenizer(data, "=\""); //$NON-NLS-1$
 while (tokenizer.hasMoreTokens()) {
                 String token = tokenizer.nextToken();
                 if (token.equalsIgnoreCase("version")) { //$NON-NLS-1$
 if (!tokenizer.hasMoreTokens()) {
                         break;
                     }
                     schemaVersion = tokenizer.nextToken();
                     break;
                 }
             }
             initializeExtractNamespace();
         }
     }

     /**
      * Handles an error state specified by the status. The collection of all logged status
      * objects can be accessed using <code>getStatus()</code>.
      *
      * @param error a status detailing the error condition
      */
     public void error(IStatus error) {
         status.add(error);
     }

     protected String translate(String key) {
         return registry.translate(key, resources);
     }

     /**
      * Fixes up the extension declarations in the given pre-3.0 plug-in or fragment to compensate
      * for extension points that were renamed between release 2.1 and 3.0.
      */
     private Extension[] fixRenamedExtensionPoints(Extension[] extensions) {
         if (extensions == null || versionAtLeast(VERSION_3_0) || RegistryProperties.getProperty(NO_EXTENSION_MUNGING) != null)
             return extensions;
         for (int i = 0; i < extensions.length; i++) {
             Extension extension = extensions[i];
             String oldPointId = extension.getExtensionPointIdentifier();
             String newPointId = (String ) extensionPointMap.get(oldPointId);
             if (newPointId != null) {
                 extension.setExtensionPointIdentifier(newPointId);
             }
         }
         return extensions;
     }

     /**
      * To preserve backward compatibility, we will only attempt to extract namespace form the name
      * if Eclipse version specified in the plugin.xml (<?eclipse version="3.2"?>) is at least 3.2.
      */
     private void initializeExtractNamespace() {
         extractNamespaces = new Boolean (versionAtLeast(VERSION_3_2)).booleanValue();
     }

     /**
      * Makes sense only for plugin.xml versions >= 3.0 (Eclipse version was introduced in 3.0).
      * Assumes that version is stored as "X1.X2.....XN" where X1 is a major version; X2 is a minor version
      * and so on.
      */
     private boolean versionAtLeast(String testVersion) {
         if (schemaVersion == null)
             return false;

         StringTokenizer testVersionTokenizer = new StringTokenizer(testVersion, "."); //$NON-NLS-1$
 StringTokenizer schemaVersionTokenizer = new StringTokenizer(schemaVersion, "."); //$NON-NLS-1$
 while (testVersionTokenizer.hasMoreTokens() && schemaVersionTokenizer.hasMoreTokens()) {
             try {
                 if (Integer.parseInt(schemaVersionTokenizer.nextToken()) < Integer.parseInt(testVersionTokenizer.nextToken()))
                     return false;
             } catch (NumberFormatException e) {
                 return false;
             }
         }
         return true;
     }
 }

